/***************************************************************************
 *   Copyright (C) 2009 by Zheng Yonglei
 *   zhengyonglei@gmail.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <cmath>

#include "TestWord.h"
#include "TestResultAnalysisDialog.h"
#include "Logger.h"

SelectPolicy::SelectPolicy(QString name):
	name(name),
	difficultyLevelMax(0),
	difficultyLevelMin(9999),
	activeFinishedWordCountMax(-1),
	activeTestingWordsCountMax(-1),
	activeUntouchedWordsCountMax(-1)
{
}

ScanRange::ScanRange(QString name):
	name(name),
	redBibleBeginList(1),
	redBibleEndList(41)
{
}

TestResultAnalysisDialog::TestResultAnalysisDialog(QDialog *parent)
    : QDialog(parent)
{
	ui.setupUi(this);

	ui.listBeginInput->setRange(0, 999);
	ui.listEndInput->setRange(0, 999);
	ui.activeFinishedWordsCount->setRange(0, 9999);
	ui.activeTestingWordsCount->setRange(0, 9999);
	ui.activeUntouchedWordsCount->setRange(0, 9999);

	ui.scanRangeComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);
	ui.selectPolicyBox->setSizeAdjustPolicy(QComboBox::AdjustToContents);

	QObject::connect(ui.scanReviewWordsButton, SIGNAL(clicked()), this, SLOT(scan()));
	QObject::connect(ui.closeDialogButton, SIGNAL(clicked()), this, SLOT(accept()));
	ui.scanResultTableView->setEditTriggers(false);
	ui.scanResultTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
	ui.scanResultTableView->setAlternatingRowColors(true);

	ui.selectedWordGroupTableView->setEditTriggers(false);
	ui.selectedWordGroupTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
	ui.selectedWordGroupTableView->setAlternatingRowColors(true);

	QObject::connect(ui.selectPolicyBox, SIGNAL(currentIndexChanged(int)), this, SLOT(selectPolicyChanged(int)));
	QObject::connect(ui.selectWordButton, SIGNAL(clicked()), this, SLOT(selectWordsToTest()));
	QObject::connect(ui.scanRangeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(scanRangeChanged(int)));
	QObject::connect(ui.testWordOKButton, SIGNAL(clicked()), this, SLOT(startTest()));
	QObject::connect(ui.browseWordOKButton, SIGNAL(clicked()), this, SLOT(browse()));

	mWordDateModel = new QStandardItemModel(this);
	mCandidatesModel = new QStandardItemModel(this);

	mTestResultAnalysisPlot = new TestResultAnalysisPlot(this);
	QObject::connect(ui.showGraphButton, SIGNAL(clicked()), this, SLOT(showGraph()));

	mForgettingCurvePlot = new ForgettingCurvePlot(this);
	QObject::connect(ui.forgettingCurveButton, SIGNAL(clicked()), this, SLOT(showForgettingCurve()));

	QAction* selectRowButton = new QAction(QIcon(":/pixmaps/arrow-right.png"), tr("Select a Row as Candidates"), this);
	connect(selectRowButton, SIGNAL(triggered()), this, SLOT(selectRowAsCandidates()));
	QAction* removeRowButton = new QAction(QIcon(":/pixmaps/arrow-left.png"), tr("Remove a Row as Candidates"), this);
	connect(removeRowButton, SIGNAL(triggered()), this, SLOT(removeRowAsCandidates()));
	QAction* clearAllRowButton = new QAction(QIcon(":/pixmaps/list-remove.png"), tr("Remove All Candidates"), this);
	connect(clearAllRowButton, SIGNAL(triggered()), this, SLOT(clearAllRowCandidates()));
	ui.selectRowButton->setDefaultAction(selectRowButton);
	ui.removeRowButton->setDefaultAction(removeRowButton);
	ui.clearAllRowButton->setDefaultAction(clearAllRowButton);

	loadSelectPolicies();
	loadScanRange();
}

TestResultAnalysisDialog::~TestResultAnalysisDialog()
{
}

int TestResultAnalysisDialog::exec()
{
	OPT.setCursorBusy(true);
	int width = this->width();
	float leftWidthRatio = 0.5f;
	QList<int> sizes;
	sizes<<width*leftWidthRatio<<width*(1.0f-leftWidthRatio);
	ui.splitter->setSizes(sizes);
	scan();
	OPT.setCursorBusy(false);
	return QDialog::exec();
}

void TestResultAnalysisDialog::showGraph()
{
	float sizeRate = 0.8f;
	mTestResultAnalysisPlot->setGeometry(OPT.scaleGeometry(this->geometry(), sizeRate, sizeRate));
	mTestResultAnalysisPlot->exec();
}

void TestResultAnalysisDialog::showForgettingCurve()
{
	float sizeRate = 0.8f;
	mForgettingCurvePlot->setGeometry(OPT.scaleGeometry(this->geometry(), sizeRate, sizeRate));
	mForgettingCurvePlot->exec();
}

void TestResultAnalysisDialog::scan()
{
	LOG<<"Scanning"<<endl;
	QString command;
	QTextStream cs(&command);

	ui.selectResultsDisplay->clear();
	int listBegin = ui.listBeginInput->value();
	int listEnd = ui.listEndInput->value();

	mTestingWordIDs.clear();
	mNewWordIDs.clear();
	mFinishedWordIDs.clear();
	mFinishedWordIDMap.clear();
	mFinalIDs.clear();

	map<QDate, int> reviewDateCount;
	map<QDate, int> newTestDateCount;
	map<int,int> testProgress;

	QDate today = OPT.getCurrendDate();

	command.clear();
	cs<<" select "
			" test_records.id, "
			" test_records.test_record, "
			" test_records.test_state "
			" from "
			" test_records,words "
			" where "
			" test_records.id=words.id "
			" and (";
	cs<<" words.list>="<<listBegin<<" ";
	cs<<" and words.list<="<<listEnd<<" );";
	DB.quickGet(command);
	for(int i=0; i<DB.getRowCount(); i++)
	{
		int id = DB.getTableValue(i,"id").toInt();
		int interval;
		int testCount;
		QDate lastDate;
		QString lastDateString;
		QString testRecordString = DB.getTableValue(i,"test_record");
		QStringList testRecordList = testRecordString.split("|", QString::SkipEmptyParts);

		if(!testRecordList.isEmpty())
		{
			lastDateString = testRecordList.last();
			lastDateString = lastDateString.left(lastDateString.lastIndexOf(':'));
			lastDate = QDate::fromString(lastDateString, DATE_FORMAT);
			interval = lastDate.daysTo(today);
			testCount = testRecordList.size();
		}
		else
		{
			interval = -1;
			testCount = 0;
		}

		//push back reciting words
		if(DB.getTableValue(i,"test_state").toInt() == int(TS_Testing))
			mTestingWordIDs.push_back(id);

		//push back untouched words
		if(DB.getTableValue(i,"test_state").toInt() == int(TS_New))
			mNewWordIDs.push_back(id);

		//classify finished words
		int lastRecordInterval = 0;
		if(	DB.getTableValue(i,"test_state").toInt() == int(TS_Finished))
		{
			mFinishedWordIDMap[interval][testCount].push_back(id);
			mFinishedWordIDs.push_back(id);

			for (int k=0; k<testRecordList.size(); k++)
			{
				map<int,int>::iterator count_it = testProgress.find(k+1);
				if (count_it == testProgress.end())
					testProgress[k+1]=1;
				else
					testProgress[k+1]++;

				QString recordItem = testRecordList[k];
				QString recordDateStr = recordItem.left(recordItem.indexOf(':'));
				QDate recordDate = QDate::fromString(recordDateStr, "yyyy-MM-dd");
				QString recordStr = recordItem.right(recordItem.size() - recordItem.indexOf(":") -1);
				assert(!recordStr.isEmpty());

				if (k == 0)
				{
					map<QDate, int>::iterator count_it = newTestDateCount.find(recordDate);
					if (count_it == newTestDateCount.end())
						newTestDateCount[recordDate]=1;
					else
						newTestDateCount[recordDate]++;
				}
				else
				{
					map<QDate, int>::iterator count_it = reviewDateCount.find(recordDate);
					if (count_it == reviewDateCount.end())
						reviewDateCount[recordDate]=1;
					else
						reviewDateCount[recordDate]++;

					int recordInterval = lastRecordInterval - recordDate.daysTo(today);
					int recordTestCount = k + 1;
					if (recordStr[0] == 'Y')
						mTestResultCounterMap[recordTestCount][recordInterval].passSampleCount++;
					else if (recordStr[0] == 'N')
						mTestResultCounterMap[recordTestCount][recordInterval].failSampleCount++;
					else
						LOG<<"Invalid record string: "<<recordStr<<endl;
				}

				lastRecordInterval = recordDate.daysTo(today);
			}
		}
	}

	fitForgettingCurve();
	setScanResultView();
	mTestResultAnalysisPlot->drawTestDateGraph(reviewDateCount, newTestDateCount);
	mTestResultAnalysisPlot->drawTestProgress(testProgress);
}

void TestResultAnalysisDialog::setScanResultView()
{
	//print scan result
	QString scanResult = QString(tr("New words")) + QString(": %1\t").arg(mNewWordIDs.size());
	scanResult.append(QString(tr("Testing words")) + QString(": %1\t").arg(mTestingWordIDs.size()));
	scanResult.append(QString(tr("Finished words")) + QString(": %1").arg(mFinishedWordIDs.size()));
	ui.scanResultLabel->setText(scanResult);
	ui.scanResultLabel->setWordWrap(true);

	//set scan result table view
	mWordDateModel->clear();
	mCandidatesModel->clear();
	QStringList headers;
	headers<<tr("ID");
	//headers<<"Last Recite Interval";
	headers<<tr("Interval");
	headers<<tr("Last Test Date");
	headers<<tr("Test Count");
	headers<<tr("Word Count");

	ui.scanResultTableView->setModel(mWordDateModel);
	ui.scanResultTableView->verticalHeader()->hide();
	mWordDateModel->setHorizontalHeaderLabels(headers);
	mCandidatesModel->setHorizontalHeaderLabels(headers);

	QHeaderView* scanResultHeader = ui.scanResultTableView->horizontalHeader();
	scanResultHeader->setSortIndicator(0, Qt::AscendingOrder);
	scanResultHeader->setSortIndicatorShown(true);
	scanResultHeader->setClickable(true);
	QObject::connect(scanResultHeader, SIGNAL(sectionClicked(int)), ui.scanResultTableView, SLOT (sortByColumn(int)));

	QHeaderView* selectedWordGroupHeader = ui.selectedWordGroupTableView->horizontalHeader();
	selectedWordGroupHeader->setSortIndicator(0, Qt::AscendingOrder);
	selectedWordGroupHeader->setSortIndicatorShown(true);
	selectedWordGroupHeader->setClickable(true);
	QObject::connect(selectedWordGroupHeader, SIGNAL(sectionClicked(int)), ui.scanResultTableView, SLOT (sortByColumn(int)));

	int groupID = 0;
	QDate today = OPT.getCurrendDate();
	for (	map<int, map<int, vector<int> > >::iterator finishedWordIDMapIt = mFinishedWordIDMap.begin();
			finishedWordIDMapIt != mFinishedWordIDMap.end();
			finishedWordIDMapIt++)
	{
		int interval = finishedWordIDMapIt->first;
		map<int, vector<int> >& testCountMap = finishedWordIDMapIt->second;
		for (	map<int, vector<int> >::iterator testCountMapIt = testCountMap.begin();
				testCountMapIt != testCountMap.end();
				testCountMapIt++)
		{
			groupID++;
			int testCount = testCountMapIt->first;
			int wordCount = testCountMapIt->second.size();
			QDate lastDate = today.addDays(0 - interval);
			QString lastDateString = lastDate.toString(DATE_FORMAT);

			QList<QStandardItem*>finishedWordsItemList;
			finishedWordsItemList<<new QStandardItem(QString("%1").arg(groupID, 3, 10, QChar('0')));
			finishedWordsItemList<<new QStandardItem(QString("%1").arg(interval, 2, 10, QChar('0')));
			finishedWordsItemList<<new QStandardItem(lastDateString);
			finishedWordsItemList<<new QStandardItem(QString("%1").arg(testCount, 2, 10, QChar('0')));
			finishedWordsItemList<<new QStandardItem(QString("%1").arg(wordCount));
			map<int , int>::iterator it = mTestBestIntv.find(testCount);
			if (it != mTestBestIntv.end())
			{
				int bestIntv = it->second;
				if (interval > bestIntv)
				{
					for (int i=0; i<finishedWordsItemList.size(); i++)
					{
						finishedWordsItemList[i]->setForeground(QBrush(Qt::red));
					}
				}
			}
			mWordDateModel->insertRow(mWordDateModel->rowCount(), finishedWordsItemList);
		}
	}

	ui.scanResultTableView->resizeColumnsToContents();
}

void TestResultAnalysisDialog::createDefaultSelectPolicyFile()
{
	QFile file("./configure/ScanPolicy.conf");
	if (file.open(QFile::WriteOnly | QFile::Truncate))
	{
		LOG<<"Writing a new select policy file."<<endl;
		QTextStream out(&file);
		out<<"[policy]Default"<<endl;
		out<<"red_bible_begin_ist=1"<<endl;
		out<<"red_bible_end_ist=41"<<endl;
		out<<"difficulty_level_max=10"<<endl;
		out<<"difficulty_level_min=9999"<<endl;
		out<<"active_finished_word_count_max=50"<<endl;
		out<<"active_reciting_word_count_max=5"<<endl;
		out<<"active_untouched_word_count_max=0"<<endl;
		out<<endl;
	}
}

void TestResultAnalysisDialog::createDefaultScanRangeFile()
{
	QFile file("./configure/ScanRange.conf");
	if (file.open(QFile::WriteOnly | QFile::Truncate))
	{
		LOG<<"writing a new policy file."<<endl;
		QTextStream out(&file);
		out<<"[policy]Default"<<endl;
		out<<"red_bible_begin_ist=1"<<endl;
		out<<"red_bible_end_ist=41"<<endl;
		out<<endl;
	}
}

void TestResultAnalysisDialog::loadScanRange()
{
	mScanRanges.clear();

	QFile file("./configure/ScanRange.conf");
	if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
	{
		LOG<<"No ScanRange.conf. Create a new one."<<endl;
		createDefaultScanRangeFile();

		if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
		{
			LOG<<"Failed to create a new ScanRange.conf."<<endl;
			QCoreApplication::quit();
		}
	}

	QTextCodec* codec = QTextCodec::codecForName("utf8");
	QTextStream in(&file);
	in.setCodec(codec);

	int curPolicyID = 0;
	while (!in.atEnd())
	{
		QString line = in.readLine();
		line = line.simplified();
		if (line.contains("[policy]"))
		{
			QString policyName = line.remove("[policy]");
			policyName = policyName.simplified();
			mScanRanges.push_back(ScanRange(policyName));
			curPolicyID = mScanRanges.size() - 1;
			continue;
		}
		if (line.contains("red_bible_begin_ist"))
		{
			QString value = line.remove("red_bible_begin_ist");
			value = value.simplified();
			value = value.remove("=");
			value = value.simplified();
			mScanRanges[curPolicyID].redBibleBeginList = value.toInt();
			continue;
		}
		if (line.contains("red_bible_end_ist"))
		{
			QString value = line.remove("red_bible_end_ist");
			value = value.simplified();
			value = value.remove("=");
			value = value.simplified();
			mScanRanges[curPolicyID].redBibleEndList = value.toInt();
			continue;
		}
	}

	if (mScanRanges.size() == 0)
	{
		LOG<<"No scan range policy available."<<endl;
		QCoreApplication::quit();
	}
	else
	{
		ui.scanRangeComboBox->clear();
		int lastPolicy = OPT.scanRangePolicy;
		if (lastPolicy >= mScanRanges.size())
			lastPolicy = 0;

		for (int i=0; i<mScanRanges.size(); i++)
			ui.scanRangeComboBox->addItem(mScanRanges[i].name);

		ui.listBeginInput->setValue(mScanRanges[lastPolicy].redBibleBeginList);
		ui.listEndInput->setValue(mScanRanges[lastPolicy].redBibleEndList);
		ui.scanRangeComboBox->setCurrentIndex(lastPolicy);
	}
}

void TestResultAnalysisDialog::loadSelectPolicies()
{
	mSelectPolicies.clear();

	QFile file("./configure/ScanPolicy.conf");
	if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
	{
		LOG<<"No ScanPolicy.conf. Create a new one."<<endl;
		createDefaultSelectPolicyFile();

		if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
		{
			LOG<<"Failed to create a new SelectPolicy.conf."<<endl;
			QCoreApplication::quit();
		}
	}

	QTextCodec* codec = QTextCodec::codecForName("utf8");
	QTextStream in(&file);
	in.setCodec(codec);

	int curPolicyID = 0;
	while (!in.atEnd())
	{
		QString line = in.readLine();
		line = line.simplified();
		if (line.contains("[policy]"))
		{
			QString policyName = line.remove("[policy]");
			policyName = policyName.simplified();
			mSelectPolicies.push_back(SelectPolicy(policyName));
			curPolicyID = mSelectPolicies.size() - 1;
			continue;
		}
		if (line.contains("difficulty_level_max"))
		{
			QString value = line.remove("difficulty_level_max");
			value = value.simplified();
			value = value.remove("=");
			value = value.simplified();
			mSelectPolicies[curPolicyID].difficultyLevelMax = value.toInt();
			continue;
		}
		if (line.contains("difficulty_level_min"))
		{
			QString value = line.remove("difficulty_level_min");
			value = value.simplified();
			value = value.remove("=");
			value = value.simplified();
			mSelectPolicies[curPolicyID].difficultyLevelMin = value.toInt();
			continue;
		}
		if (line.contains("active_finished_word_count_max"))
		{
			QString value = line.remove("active_finished_word_count_max");
			value = value.simplified();
			value = value.remove("=");
			value = value.simplified();
			mSelectPolicies[curPolicyID].activeFinishedWordCountMax = value.toInt();
			continue;
		}
		if (line.contains("active_reciting_word_count_max"))
		{
			QString value = line.remove("active_reciting_word_count_max");
			value = value.simplified();
			value = value.remove("=");
			value = value.simplified();
			mSelectPolicies[curPolicyID].activeTestingWordsCountMax = value.toInt();
			continue;
		}
		if (line.contains("active_untouched_word_count_max"))
		{
			QString value = line.remove("active_untouched_word_count_max");
			value = value.simplified();
			value = value.remove("=");
			value = value.simplified();
			mSelectPolicies[curPolicyID].activeUntouchedWordsCountMax = value.toInt();
			continue;
		}
	}

	if (mSelectPolicies.size() == 0)
	{
		LOG<<"No scan policy available."<<endl;
		QCoreApplication::quit();
	}
	else
	{
		ui.selectPolicyBox->clear();
		int lastPolicy = OPT.selectWordPolicy;
		if (lastPolicy >= mSelectPolicies.size())
			lastPolicy = 0;

		for (int i=0; i<mSelectPolicies.size(); i++)
			ui.selectPolicyBox->addItem(mSelectPolicies[i].name);

		ui.activeFinishedWordsCount->setValue(mSelectPolicies[lastPolicy].activeFinishedWordCountMax);
		ui.activeTestingWordsCount->setValue(mSelectPolicies[lastPolicy].activeTestingWordsCountMax);
		ui.activeUntouchedWordsCount->setValue(mSelectPolicies[lastPolicy].activeUntouchedWordsCountMax);
		ui.selectPolicyBox->setCurrentIndex(lastPolicy);
	}
}

void TestResultAnalysisDialog::selectPolicyChanged(int index)
{
	if (index != OPT.selectWordPolicy)
	{
		ui.activeFinishedWordsCount->setValue(mSelectPolicies[index].activeFinishedWordCountMax);
		ui.activeTestingWordsCount->setValue(mSelectPolicies[index].activeTestingWordsCountMax);
		ui.activeUntouchedWordsCount->setValue(mSelectPolicies[index].activeUntouchedWordsCountMax);
		OPT.selectWordPolicy = index;
	}
}

void TestResultAnalysisDialog::scanRangeChanged(int index)
{
	if (index != OPT.scanRangePolicy)
	{
		ui.listBeginInput->setValue(mScanRanges[index].redBibleBeginList);
		ui.listEndInput->setValue(mScanRanges[index].redBibleEndList);
		OPT.scanRangePolicy = index;
	}
}

void TestResultAnalysisDialog::selectRowAsCandidates()
{
	vector<int>& candidates = ui.scanResultTableView->getSelectedRows();
	for (int i=candidates.size()-1; i>=0; i--)
	{
		QList<QStandardItem*>curRow = mWordDateModel->takeRow(candidates[i]);
		mCandidatesModel->insertRow(mCandidatesModel->rowCount(), curRow);
		LOG<<"Add row: "<<candidates[i]+1<<endl;
	}

	mCandidatesModel->sort(2);
	mCandidatesModel->sort(1);

	ui.selectedWordGroupTableView->setModel(mCandidatesModel);
	ui.selectedWordGroupTableView->resizeColumnsToContents();
	ui.selectedWordGroupTableView->verticalHeader()->hide();
}

void TestResultAnalysisDialog::removeRowAsCandidates()
{
	vector<int>& removeWords = ui.selectedWordGroupTableView->getSelectedRows();
	for (int i=removeWords.size()-1; i>=0; i--)
	{
		QList<QStandardItem*>curRow = mCandidatesModel->takeRow(removeWords[i]);
		mWordDateModel->insertRow(mWordDateModel->rowCount(), curRow);
	}

	mWordDateModel->sort(2);
	mWordDateModel->sort(1);
}

void TestResultAnalysisDialog::clearAllRowCandidates()
{
	int rowCount = mCandidatesModel->rowCount();
	for (int i=0; i<rowCount; i++)
	{
		QList<QStandardItem*>curRow = mCandidatesModel->takeRow(0);
		mWordDateModel->insertRow(0, curRow);
	}

	mWordDateModel->sort(2);
	mWordDateModel->sort(1);
}

void TestResultAnalysisDialog::selectWordsToTest()
{
	mFinishedWordIDs.clear();
	ui.selectResultsDisplay->clear();

	if (mTestingWordIDs.empty() && mNewWordIDs.empty() && mFinishedWordIDMap.empty())
	{
		LOG<<"Error! All id vectors are empty."<<endl;
		assert(0);
	}

	QString command;
	QTextStream cs(&command);

	for (int i=0; i<mCandidatesModel->rowCount(); i++)
	{
		int interval = mCandidatesModel->item(i, 1)->text().toInt();
		int testCount = mCandidatesModel->item(i, 3)->text().toInt();

		map<int, map<int, vector<int> > >::iterator finishedWordIDMapIt = mFinishedWordIDMap.find(interval);
		if (finishedWordIDMapIt != mFinishedWordIDMap.end())
		{
			map<int, vector<int> >& testCountMap = finishedWordIDMapIt->second;
			map<int, vector<int> >::iterator testCountMapIt = testCountMap.find(testCount);
			if (testCountMapIt != testCountMap.end())
			{
				vector<int>& wordIDVect = testCountMapIt->second;
				for (int j=0; j<wordIDVect.size(); j++)
				{
					int id = wordIDVect[j];
					mFinishedWordIDs.push_back(id);
				}
			}
		}
	}

	LOG<<"mFinishedWordIDs.size()="<<mFinishedWordIDs.size()<<endl;

	int activeFinishedWordCount = ui.activeFinishedWordsCount->value();
	int activeTestingWordsCount = ui.activeTestingWordsCount->value();
	int activeUntouchedWordsCount = ui.activeUntouchedWordsCount->value();

	if(activeFinishedWordCount > mFinishedWordIDs.size())
		activeFinishedWordCount = mFinishedWordIDs.size();

	if(activeTestingWordsCount > mTestingWordIDs.size())
		activeTestingWordsCount = mTestingWordIDs.size();

	if(activeUntouchedWordsCount > mNewWordIDs.size())
		activeUntouchedWordsCount = mNewWordIDs.size();

	vector<int> finishedWordIDs = mFinishedWordIDs;
	vector<int> recitingWordIDs = mTestingWordIDs;
	vector<int> newWordIDs = mNewWordIDs;

	mFinalIDs.clear();
        //srand((int)time(0));
        srand(0);
	vector<int>::iterator id_it;
	for(int i=0; i<activeFinishedWordCount; i++)
	{
		int wordIndex = 0+(int)(float(finishedWordIDs.size())*rand()/(RAND_MAX+1.0));
		mFinalIDs.push_back(finishedWordIDs[wordIndex]);
		id_it = finishedWordIDs.begin() + wordIndex;
		finishedWordIDs.erase(id_it);
	}

	for(int i=0; i<activeTestingWordsCount; i++)
	{
		int wordIndex = 0+(int)(float(recitingWordIDs.size())*rand()/(RAND_MAX+1.0));
		mFinalIDs.push_back(recitingWordIDs[wordIndex]);
		id_it = recitingWordIDs.begin() + wordIndex;
		recitingWordIDs.erase(id_it);
	}

	for(int i=0; i<activeUntouchedWordsCount; i++)
	{
		int wordIndex = 0+(int)(float(newWordIDs.size())*rand()/(RAND_MAX+1.0));
		mFinalIDs.push_back(newWordIDs[wordIndex]);
		id_it = newWordIDs.begin() + wordIndex;
		newWordIDs.erase(id_it);
	}

	QString result;
	result.append(QString("Select %1(").arg(activeFinishedWordCount));
	result.append(QString("%1) 'finished' words.<br>").arg(mFinishedWordIDs.size()));
	result.append(QString("Select %1(").arg(activeTestingWordsCount));
	result.append(QString("%1) 'reciting' words.<br>").arg(mTestingWordIDs.size()));
	result.append(QString("Select %1(").arg(activeUntouchedWordsCount));
	result.append(QString("%1) 'new' words.<br>").arg(mNewWordIDs.size()));
	ui.selectResultsDisplay->insertHtml(result);
}

void TestResultAnalysisDialog::startTest()
{
	TestResultAnalysisDialog::done(TT_Test);
}

void TestResultAnalysisDialog::browse()
{
	TestResultAnalysisDialog::done(TT_Browse);
}

void TestResultAnalysisDialog::done(int taskType)
{
	if( (mFinalIDs.size() >= OPT.testWordCountMin && taskType == TT_Test)
		|| (taskType == TT_Browse))
	{
		//LOG<<"mFinalIDs.size()("<<mFinalIDs.size()<<") > OPT.testWordCountMin("<<OPT.testWordCountMin<<")"<<endl;
		LOG<<"done("<<taskType<<")"<<endl;
		QString command;
		command = "update test_records set is_active=0; ";
		DB.quickUpdate(command);
		DB.quickUpdate("BEGIN");
		for(int i=0; i<mFinalIDs.size(); i++)
		{
			int wordID = mFinalIDs[i];
			command = QString("update test_records set is_active = 1 where id = %1;").arg(wordID);
			DB.quickUpdate(command);
		}
		DB.quickUpdate("COMMIT");
		QDialog::done(taskType);
	}
	else if (taskType == TT_Test && mFinalIDs.size() < OPT.testWordCountMin )
	{
		LOG<<"Not enough word to start memorizing test"<<endl;
		QString message;
		message = "Not enough word to start memorizing test";
		MessageBox::showWarning(QMessageBox::Warning,QString("Error!"),message,12,this);
	}
	else
		QDialog::done(taskType);
}

void TestResultAnalysisDialog::fitForgettingCurve()
{
	//LOG<<"[testCount] [interval] [pass] [fail]"<<endl;
	mTestBestIntv.clear();
	for (	map<int, map<int, TestResultCounter> >::iterator test_count_it = mTestResultCounterMap.begin();
			test_count_it != mTestResultCounterMap.end();
			test_count_it++ )
	{
		int testCount = test_count_it->first;
		//LOG<<"testCount="<<testCount<<endl;
		int sampleCountAvg = 0;
		map<int, TestResultCounter>& intvMap = test_count_it->second;
		for (	map<int, TestResultCounter>::iterator intv_it = intvMap.begin();
				intv_it != intvMap.end();
				intv_it++ )
		{
			TestResultCounter& counter = intv_it->second;
			sampleCountAvg += counter.sampleCount();
		}

		sampleCountAvg = sampleCountAvg/intvMap.size();
		//LOG<<testCount<<"\tsampleCountAvg="<<sampleCountAvg<<endl;

		int tryDay = 1;
		int tryDayMax = 1000;
		float squareSum;
		float lastSquareSum = 999.9f;
		while(tryDay <= tryDayMax)
		{
			squareSum = 0.0f;
			for (	map<int, TestResultCounter>::iterator intv_it = intvMap.begin();
					intv_it != intvMap.end();
					intv_it++ )
			{
				int interval = intv_it->first;
				TestResultCounter& counter = intv_it->second;
				if (counter.sampleCount() >= sampleCountAvg)
				{
					counter.isValid = true;
					float temp = counter.passRate() - exp(-1.0f/float(tryDay) * float(interval));
					squareSum += temp * temp;
				}
				else
					counter.isValid = false;
			}

			if (lastSquareSum < squareSum || lastSquareSum <= 0.001f)
				break;

			//LOG<<testCount<<"\tday="<<day<<endl;
			lastSquareSum = squareSum;
			tryDay++;
		}

		LOG<<"Final: testCount="<<testCount<<"\ttryDay="<<tryDay - 1<<"\tlastSquareSum="<<lastSquareSum<<endl;
		mTestBestIntv[testCount] = tryDay - 1;
	}

	mForgettingCurvePlot->setData(mTestResultCounterMap, mTestBestIntv);
}
